//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/types/typetable.h"
#include "sagittarius/io/io.h"
#include <string.h>
#include <malloc.h>

typecode typecode_set_cnt(typecode t, uint8 c) {
    t &= TYPECODE_CNT_ANTIMASK;
    t |= (typecode)c << TYPECODE_CNT_SHIFT;
    return t;
}

uint8 typecode_get_cnt(typecode t) {
    return t >> TYPECODE_CNT_SHIFT;
}

/*const char* type_builtinstr[SG_BUILTIN_TYPE_END] = {
    "empty",
    "obj",
    "ref",
    "int",
    "real",
    "str",
    //"empty",
    "nil"
};*/

// -- Type Record --


/*C_INLINE typerec make_ttable_record(typetable* st, typedesc* td) {
    typerec p;
    p.id.k = sg_lex_hashbuf(name);
    p.rep = sg_lex_copybuf(name);
    p.id.u = ttable_count_k(st, p.id, name);
    p.f = NULL;
    p.c = NULL;
    //WLSEMA(st->sema); //no modifications to the hash table internals while this is running
    
    //WUSEMA(st->sema);
    return p;
}*/

#if 0
C_INLINE typerec make_ttable_recordl(typetable* st, constLexBuf name, size_t l) {
    typerec p;
    p.id.k = sg_lex_hashbufl(name, l);
    p.rep = sg_lex_copybufl(name, l);
    p.id.u = ttable_count_k(st, p.id, name);
    p.f = NULL;
    p.c = NULL;
    //WLSEMA(st->sema); //no modifications to the hash table internals while this is running
    
    //WUSEMA(st->sema);
    return p;
}

C_INLINE typerec make_ttable_tmp_recordl(typetable* st, constLexBuf name, size_t l) {
    typerec p;
    p.id.k = sg_lex_hashbufl(name, l);
    p.rep = name;
    p.id.u = ttable_count_builtinl(st, p.id, name, l);
    p.f = NULL;
    p.c = NULL;
    //WLSEMA(st->sema); //no modifications to the hash table internals while this is running
    
    //WUSEMA(st->sema);
    return p;
}

C_INLINE typerec make_ttable_record_codel(typetable* st, typecode k, LexBuf rep, size_t l) {
    typerec p;
    p.id.k = k;
    p.rep = rep;
    p.id.u = ttable_count_builtinl(st, p.id, rep, l);
    p.f = NULL;
    p.c = NULL;
    //WLSEMA(st->sema); //no modifications to the hash table internals while this is running
    
    //WUSEMA(st->sema);
    return p;
}
#endif

int fieldrec_cmp(typedesc* a, typedesc* b) {
    if(a->t == b->t && a->i == b->i)
        return 1;
    else
        return 0;
}

int typedesc_cmp(typedesc* a, typedesc* b) {
    uint32 i;
    if(a->n != b->n)
        return 0;
    for(i=0; i<a->n; ++i) {
        if(!fieldrec_cmp(a->f[i], b->f[i]))
            return 0;
    }
    return 1;
}

void typedesc_grow(typedesc* d) {
    
}

int typedesc_push_field(typedesc* d, fieldrec f) {
    if(!d->f || d->n == d->c)
        typedesc_grow(d);
}

#if SAGITTARIUS_COMPILER != SAGITTARIUS_COMPILER_MSVC
inline
#endif
void delete_ttable_record(typerec* p) {
    if(p.rep)
        free(p.rep);
    /*if(p.c)
        free(p.c);
    if(p.f)
        free(p.f);*/
    free(p);
}

fieldrec* typeref_fields_rehash(fieldrec* f, uint32 n, uint32 depth) {
    AN(f);
    free(f);
    f = malloc(sizeof(fieldrec)*(depth+1)*n); //depth+1 b/c the first elt gives cell count
    return f;
}

void typeref_init_fields(typerec* p) {
    AZ(p->f, "Already have fields");
    p->o = 0;
    p->n = 4;
    p->d = 2;
    p->f = typeref_fields_rehash(p->f, p->n, p->d);
    AZ(p->c, "Already have counts");
    p->c = malloc(sizeof(uint32)*p->n);
}

void typerec_add_field(typerec* p, uint64 sym, uint64 typecode, size_t bytes) {
    // bucket
    fieldrec* b;
    // index
    uint32 i=sym%p->n;
    
    if(!p->f)
        typeref_init_fields(p);
    
    b = p->f + i;
    
    // enough room in bucket?
    if(p->c[i] < p->d) {
        // insert element
        b += p->c[i]++;
        b->t = typecode;
        b->i = p->o;
        p->o += (bytes < 8) ? 8 : bytes;
    }
}

/// create_type_table
typetable* create_type_table() {
    typetable* tt = ttable_rehash(NULL, 16, 4);
    init_type_table(tt);
    return tt;
}

/// init_type_table
void init_type_table(typetable* tt) {
    /*uint32 i;
    LexBuf b;
    typerec e;
    for(i=0; i<SG_BUILTIN_TYPE_END; ++i) {
        b = sg_lex_str2buf(type_builtinstr[i]);
        e=make_ttable_record(tt, b);
        e.builtin = 1;
        e.builtinref = i;
        _ttable_insert(tt, e);
        LX_FREE_BUFFER(b);
    }*/
}

/// finalize_ttable_bucket
void finalize_ttable_bucket(ttable_bucket* b) {
    uint32 i;
    for(i=0; i<b->n; ++i) {
        LX_FREE_BUFFER(b->e[i].rep);
    }
    free(b->e);
}

/// finalize_type_table
void finalize_type_table(typetable* tt) {
    uint32 i;
    for(i=0; i<tt->nb; ++i) {
        finalize_ttable_bucket(&tt->b[i]);
    }
    free(tt->b);
    free(tt);
}

/// ttable_count_k
/*uint8 ttable_count_k(typetable* tt, typehash id, constLexBuf name) {
    ttable_bucket* b;
    uint64 i;
    uint64 c=0;
    typehash mask;
    //the order is very important here
    mask.k = -1;
    mask.u = 0;
    //masks out typehash.u
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(id.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if((b->e[i].id.k&mask.k) == (id.k&mask.k) && !sg_lex_bufeq(name, b->e[i].rep))
            ++c;
    }
    
    RUSEMA(tt->sema);
    
    return c;
}*/

uint8 ttable_count_code(typetable* tt, typecode t) {
    ttable_bucket* b;
    uint64 i;
    uint64 c=0;
    typehash mask;
    //the order is very important here
    mask.k = -1;
    mask.u = 0;
    //masks out typehash.u
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(id.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if((b->e[i].id.k&mask.k) == (id.k&mask.k))
            ++c;
    }
    
    RUSEMA(tt->sema);
    
    return c;
}

/// ttable_count_builtinl
/*uint8 ttable_count_builtinl(typetable* tt, typehash id, constLexBuf name, size_t l) {
    ttable_bucket* b;
    uint64 i;
    uint64 c=0;
    typehash mask;
    //the order is very important here
    mask.k = -1;
    mask.u = 0;
    //masks out typehash.u
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(id.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].builtin && (b->e[i].id.k&mask.k) == (id.k&mask.k) && !sg_lex_bufeql(name, b->e[i].rep, l))
            ++c;
    }
    
    RUSEMA(tt->sema);
    
    return c;
}*/

/// ttable_query
/*ttablequery ttable_query(typetable* tt, typehash e) {
    ttable_bucket* b;
    uint64 i;
    ttablequery q;
    q.b = 0;
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(e.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.k) {
            q.b = 1;
            q.t = b->e[i];
            break; //can return early
        }
    }
    
    RUSEMA(tt->sema);
    
    return q;
}*/

/// ttable_check
/*ttablecheck ttable_check(typetable* tt, typehash e) {
    ttable_bucket* b;
    uint64 i;
    ttablecheck c;
    c.b = 0;
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(e.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.k) {
            c.b = 1;
            c.id = b->e[i].id;
            break; //can return early
        }
    }
    
    RUSEMA(tt->sema);
    
    return c;
}*/

/// ttable_check
/*typerec* ttable_check_namel(typetable* tt, constLexBuf name, size_t l) {
    typerec* q;
    ttable_bucket* b;
    uint64 i;
    typerec e=make_ttable_recordl(tt, name, l);
    q.b = 0;
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(e.id.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.id.k) {
            q.b = 1;
            delete_ttable_record(e);
            break; //can return early
        }
    }
    
    RUSEMA(tt->sema);
    
    q.t = e;
    
    return q;
}*/

/// ttable_check_namel_eph
//TODO: can use ttablecheck as return type
/*typerec* ttable_check_namel_eph(typetable* tt, constLexBuf name, size_t l) {
    typerec* q;
    ttable_bucket* b;
    uint64 i;
    typerec e=make_ttable_tmp_recordl(tt, name, l);
    q.b = 0;
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(e.id.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.id.k) {
            q = b->e[i];
            // TODO: this does not check type
            RUSEMA(tt->sema);
            return q;
        }
    }
    
    RUSEMA(tt->sema);
    
    return NULL;
}*/

/// ttable_name
/*LexBuf ttable_name(typetable* tt, typehash e) {
    ttable_bucket* b;
    uint32 i;
    
    RLSEMA(tt->sema);
    
    b=&tt->b[TTABLE_MORPH_HASH(e.k)%tt->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.k) {
            return b->e[i].rep;
        }
    }
    
    RUSEMA(tt->sema);
    
    return NULL;
}*/

/// _ttable_insert
int _ttable_insert(typetable* tt, typerec e) {
    ttable_bucket* b = NULL;
    size_t bn;
    typerec* tmp;
    //uint32 i;
    uint32 depth;
    
    WLSEMA(tt->sema); //no modifications to the hash table internals while this is running
    //we can do reader lock on table & writer lock on bucket or just writer lock on table
    
     //no modifications to the hash table internals while this is running
    
    bn = e.id;
    /*SG_WRITE(2, "_ttable_insert: e.id.k = ");
    sg_writeUInt64(2, e.id.k);
    SG_WRITE(2, "\n");
    SG_WRITE(2, "_ttable_insert: TTABLE_MORPH_HASH(e.id.k) = ");
    sg_writeUInt64(2, TTABLE_MORPH_HASH(e.id.k));
    SG_WRITE(2, "\n");
    SG_WRITE(2, "_ttable_insert: bn = ");
    sg_writeUInt64(2, bn);
    SG_WRITE(2, "\n");*/
    b = &tt->b[bn]; //NOTE: not safe to do this before tt sema is locked
    
    //WLSEMA(b->sema);
    
    /*for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.id.k) {
            return TTABLE_COLLISION;
        }
    }*/
    if(b->n >= b->depth) {
        //need to reallocate
        depth = b->depth<<1;
        tmp = malloc(depth*sizeof(typerec));
        memcpy(tmp, b->e, b->depth*sizeof(typerec));
        b->depth = depth;
        free(b->e);
        b->e = tmp;
    }
    b->e[b->n++] = e;
    
    //WUSEMA(b->sema);
    WUSEMA(tt->sema);
    
    
    return 0;
}

/// ttable_get_or_insertl
#if 0
typehash ttable_get_or_insertl(typetable* tt, constLexBuf name, size_t l) {
    ttablecheck c;
    typerec e=make_ttable_recordl(tt, name, l);
    c = ttable_check(tt, e.id);
    if(c.b) {
        delete_ttable_record(e);
        return c.id;
    } else {
        _ttable_insert(tt, e);
    }
    return e.id;
}
#endif

typecode ttable_new_type(typetable* tt, typecode t) {
    typecode h;
    uint8 c = ttable_count_code(tt, t);
    h = typecode_set_cnt(h, c);
    
}

/// ttable_rehash
typetable* ttable_rehash(typetable* tt, uint64 nbuckets, uint64 depth) {
    typetable* h;
    ttable_bucket* b;
    uint64 i, j;
    
    if(tt)
        RLSEMA(tt->sema);
    
    h = malloc(sizeof(typetable));
    AN(h);
    
    INITSEMA(h->sema);
    
    h->nb = nbuckets;
    h->b = malloc(nbuckets*sizeof(ttable_bucket));
    for(i=0; i<h->nb; ++i) {
        h->b[i].n = 0;
        h->b[i].depth = depth;
        h->b[i].e = malloc(depth*sizeof(typerec*));
        
    }
    
    //insert old values
    if(tt != NULL) {
        for(i=0; i<tt->nb; ++i) {
            b = &tt->b[i];
            for(j=0; j<b->n; ++j) {
                _ttable_insert(h, b->e[j]);
            }
        }
    }
    
    /*if(tt && tt->nr) {
        h->e = malloc(tt->c*sizeof(
        memcpy
    }*/
    
    if(tt)
        RUSEMA(tt->sema);
    
    return h;
}

/// get_or_add_type
/*typehash ttable_get_typel(typetable* tt, constLexBuf b) {
    
}*/

#if 0
void ttablePrintSym(int fd, typetable* tt, typehash e) {
    /*sg_writeUInt64(fd, e.k);
    SG_WRITE(fd, "(");*/
    LX_WRITE_BUF(fd, ttable_name(tt, e));
    //SG_WRITE(fd, ")\n");
}

void typetable_dumpstate(int fd, typetable* h) {
    uint32 i, j;
    SG_WRITE(fd, "Type Table");
    SG_WRITE(fd, "\n  No buckets: ");
    sg_writeInt(fd, (int)h->nb);
    SG_WRITE(fd, "\n");
    for(i=0; i<h->nb; ++i) {
        SG_WRITE(fd, "    Bucket ");
        sg_writeUInt(fd, i);
        SG_WRITE(fd, "\n      Number ");
        sg_writeUInt(fd, h->b[i].n);
        SG_WRITE(fd, "\n      Depth ");
        sg_writeUInt(fd, h->b[i].depth);
        SG_WRITE(fd, "\n");
        
        for(j=0; j<h->b[i].n; ++j) {
            SG_WRITE(fd, "        Key ");
            sg_writeMemSegHex(fd, &h->b[i].e[j].id.k, 8);
            SG_WRITE(fd, "\n        Name ");
            LX_WRITE_BUF(fd, h->b[i].e[j].rep);
            SG_WRITE(fd, "\n");
        }
    }
}
#endif
